#include "puntaelena_graphsupport_NativeGraphSupportBridge.h"

#include <iostream>

#include "accumulsupport/AccumulSupport.h"

#include "graphsupport/graph/FacilitiesGraph.h"
#include "graphsupport/graph/operation/CompoundGraphOperation.h"
#include "graphsupport/graph/operation/GenBipartLinkNear.h"
#include "graphsupport/graph/generate/DefaultGraphGenerator.h"
#include "graphsupport/graph/operation/AddChpExpWeightsOperation.h"
#include "graphsupport/graph/operation/MakeSymmOperation.h"
#include "graphsupport/graph/operation/GeneratePricesOperation.h"
#include "graphsupport/graph/operation/TriangleInequalityCloseOperation.h"

#include "graphsupport/solve/facilitylocation/BinaryRunFacilityLocationSolver.h"
#include "graphsupport/solve/facilitylocation/PrimalDualFacilitiesLocationSolver.h"
#include "graphsupport/solve/facilitylocation/CompoundFacilitiesSolver.h"
#include "graphsupport/solve/facilitylocation/ChooseCheapestFacilityHeuristic.h"
#include "graphsupport/solve/facilitylocation/RemoveUnusedFacilityHeuristic.h"

/**
 *
 * Java integration
 *
 */

// TODO: move to different folder

JNIEXPORT jint JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_defaultExpensiveCheapGeneratorPtr
	( JNIEnv *, jclass )
{
	CompoundGraphOperation * mainOp = new CompoundGraphOperation();
	IGraphOperationPtr mainOpPtr( mainOp );
	GenBipartLinkNear * bipartGen = new GenBipartLinkNear();
	mainOp->addSubOperation( IGraphOperationPtr( bipartGen ) );
	mainOp->addSubOperation( IGraphOperationPtr( new AddChpExpWeightsOperation() ) );
	mainOp->addSubOperation( IGraphOperationPtr( new MakeSymmOperation() ) );
	mainOp->addSubOperation( IGraphOperationPtr( new GeneratePricesOperation() ) );
	mainOp->addSubOperation( IGraphOperationPtr( new TriangleInequalityCloseOperation( true ) ) );

	DefaultGraphGenerator * defGenerator = new DefaultGraphGenerator( mainOpPtr );
	IGraphGeneratorPtr genPtr( defGenerator );

	IGraphGeneratorPtr * resultPtr =
		new IGraphGeneratorPtr( genPtr );
	int resultInt = reinterpret_cast< int >( resultPtr );
	return resultInt;
}

JNIEXPORT jobject JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_nativeGenerate
	( JNIEnv * env, jclass, jint generatorIntPtr, jobject genGrSett )
{
	IGraphGeneratorPtr * generatorPtr =
		reinterpret_cast< IGraphGeneratorPtr * >( generatorIntPtr );

	jclass genGrSettClazz = env->FindClass( "puntaelena/graphsupport/GenerateGrSettings" );
	assert( genGrSettClazz );

	jfieldID firstBipartField = env->GetFieldID( genGrSettClazz, "firstBipart", "I" );
	assert( firstBipartField );
	jfieldID secondBipartField = env->GetFieldID( genGrSettClazz, "secondBipart", "I" );
	assert( secondBipartField );
	jfieldID neighborsBegField = env->GetFieldID( genGrSettClazz, "neighborCountBeg", "I" );
	assert( neighborsBegField );
	jfieldID neighborsRangeField = env->GetFieldID( genGrSettClazz, "neighborCountRange", "I" );
	assert( neighborsRangeField );
	jfieldID cheapBegField = env->GetFieldID( genGrSettClazz, "cheapEdgeBeg", "D" );
	assert( cheapBegField );
	jfieldID expensiveBegField = env->GetFieldID( genGrSettClazz, "expensiveEdgeBeg", "D" );
	assert( expensiveBegField );
	jfieldID cheapRangeField = env->GetFieldID( genGrSettClazz, "cheapEdgeRange", "D" );
	assert( cheapRangeField );
	jfieldID expensiveRangeField = env->GetFieldID( genGrSettClazz, "expensiveEdgeRange", "D" );
	assert( expensiveRangeField );
	jfieldID randomSeedField = env->GetFieldID( genGrSettClazz, "randomSeed", "D" );
	assert( randomSeedField );
	jfieldID facilityPriceBegField = env->GetFieldID( genGrSettClazz, "facilityPriceBeg", "D" );
	assert( facilityPriceBegField );
	jfieldID facilityPriceRangeField = env->GetFieldID( genGrSettClazz, "facilityPriceRange", "D" );
	assert( facilityPriceRangeField );
	jfieldID triangleIneqEnabledField = env->GetFieldID( genGrSettClazz, "triangleIneqEnabled", "Z" );
	assert( triangleIneqEnabledField );

	jint firstBipart = env->GetIntField( genGrSett, firstBipartField );
	jint secondBipart = env->GetIntField( genGrSett, secondBipartField );
	jint neighborsBeg = env->GetIntField( genGrSett, neighborsBegField );
	jint neighborsRange = env->GetIntField( genGrSett, neighborsRangeField );
	jdouble cheapBeg = env->GetDoubleField( genGrSett, cheapBegField );
	jdouble expensiveBeg = env->GetDoubleField( genGrSett, expensiveBegField );
	jdouble cheapRange = env->GetIntField( genGrSett, cheapRangeField );
	jdouble expensiveRange = env->GetIntField( genGrSett, expensiveRangeField );
	jdouble randomSeedD = env->GetDoubleField( genGrSett, randomSeedField );
	int randomSeed = accumf::memXOR< double, int >( randomSeedD );
	jdouble facilityPriceBeg = env->GetDoubleField( genGrSett, facilityPriceBegField );
	jdouble facilityPriceRange = env->GetDoubleField( genGrSett, facilityPriceRangeField );
	jboolean triangleIneqEnabled = env->GetBooleanField( genGrSett, triangleIneqEnabledField );

	FacilitiesGraph fgraph;
	IOperationSettings oset( firstBipart, secondBipart );
	oset.neighborsBeg = neighborsBeg;
	oset.neighborsRange = neighborsRange;
	oset.cheapBeg = ( int ) cheapBeg;
	oset.expensiveBeg = ( int ) expensiveBeg;
	oset.cheapRange = cheapRange;
	oset.expensiveRange = expensiveRange;
	oset.facilityPriceBeg = facilityPriceBeg;
	oset.facilityPriceRange = facilityPriceRange;
	oset.triangleIneqEnabled = triangleIneqEnabled;
	oset.setRandomSeed( randomSeed );
	( * generatorPtr )->generateGraph( fgraph, oset );
	int vertexCount = fgraph.getVertexCount();

	jclass graphClazz = env->FindClass( "puntaelena/graphsupport/FacilitiesGraph" );
	assert( graphClazz );
	jmethodID constructorJMID = env->GetMethodID( graphClazz, "<init>", "(II)V" );
	assert( constructorJMID );
	jobject newJGraph = env->NewObject( graphClazz, constructorJMID, fgraph.getCitiesCount(), fgraph.getFacilitiesCount() );
	assert( newJGraph );

	jmethodID getAdjJMID = env->GetMethodID( graphClazz, "getAdjacentMatrixArr", "()[[D" );
	assert( getAdjJMID );
	jobject adjMatr = env->CallObjectMethod( newJGraph, getAdjJMID );
	assert( adjMatr );

	for ( int i = 0; i < vertexCount; i++ )
	{
		jobject adjMatrStr = env->GetObjectArrayElement( ( jobjectArray )adjMatr, i );
		env->SetDoubleArrayRegion( ( jdoubleArray )adjMatrStr, 0, vertexCount, fgraph.getAdjacentMatrixArr()[i] );
	}

	jmethodID getFcltPricesJMID = env->GetMethodID( graphClazz, "getFacilitiesPrices", "()[D" );
	assert( getFcltPricesJMID );
	jobject fcltPrices = env->CallObjectMethod( newJGraph, getFcltPricesJMID );
	assert( fcltPrices );
	env->SetDoubleArrayRegion( ( jdoubleArray )fcltPrices, 0, fgraph.getFacilitiesCount(), & fgraph.facilitiesPrices[0] );

	env->SetDoubleField( genGrSett, randomSeedField, oset.randomDouble() );

	return newJGraph;
}

JNIEXPORT void JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_nativeDisposeGenerator
  (JNIEnv *, jclass, jint generatorIntPtr)
{
	IGraphGeneratorPtr * generatorPtr =
		reinterpret_cast< IGraphGeneratorPtr * >( generatorIntPtr );
	delete generatorPtr;
	generatorPtr = 0;
}

JNIEXPORT jint JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_binarySolverPtr
  ( JNIEnv *, jclass )
{
	IFacilityLocationSolverPtr * solverPtr =
		new IFacilityLocationSolverPtr( new BinaryRunFacilityLocationSolver() );
	int resultInt = reinterpret_cast< int >( solverPtr );
	return resultInt;
}

JNIEXPORT jint JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_trivialSolverPtr
  ( JNIEnv *, jclass )
{
	CompoundFacilitiesSolver * trivialSolver = new CompoundFacilitiesSolver();
	IFacilityLocationSolverPtr solverPtr( trivialSolver );

	trivialSolver->addSubSolver( IFacilityLocationSolverPtr( new ChooseCheapestFacilityHeuristic() ) );
	trivialSolver->addSubSolver( IFacilityLocationSolverPtr( new RemoveUnusedFacilityHeuristic() ) );

	int resultInt = reinterpret_cast< int >( new IFacilityLocationSolverPtr( solverPtr ) );
	return resultInt;
}

JNIEXPORT jint JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_factor3SolverPtr
  (JNIEnv *, jclass)
{
	int resultInt = reinterpret_cast< int >(
		new IFacilityLocationSolverPtr( new PrimalDualFacilitiesLocationSolver() )
	);
	return resultInt;
}

JNIEXPORT void JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_nativeSolve
  ( JNIEnv * env, jclass, jint solverIntPtr, jobject jfgraph, jbooleanArray isFacilityOpen, jintArray cityWitness )
{
	IFacilityLocationSolverPtr * solverPtr =
		reinterpret_cast< IFacilityLocationSolverPtr * >( solverIntPtr );

	jclass graphClazz = env->FindClass( "puntaelena/graphsupport/FacilitiesGraph" ); // TODO: dublicated puntaelena/graphsupport/FacilitiesGraph
	assert( graphClazz );

	jmethodID getCitiesCountJMID = env->GetMethodID( graphClazz, "getCitiesCount", "()I" );
	assert( getCitiesCountJMID );
	int citiesCount = env->CallIntMethod( jfgraph, getCitiesCountJMID );
	jmethodID getFacilitiesCountJMID = env->GetMethodID( graphClazz, "getFacilitiesCount", "()I" );
	assert( getFacilitiesCountJMID );
	int facilitiesCount = env->CallIntMethod( jfgraph, getFacilitiesCountJMID );

	FacilitiesGraph fgraph( citiesCount, facilitiesCount );

	jmethodID getAdjJMID = env->GetMethodID( graphClazz, "getAdjacentMatrixArr", "()[[D" );
	assert( getAdjJMID );
	jobject adjMatr = env->CallObjectMethod( jfgraph, getAdjJMID );
	assert( adjMatr );

	int vertexCount = fgraph.getVertexCount();

	for ( int i = 0; i < vertexCount; i++ )
	{
		jobject adjMatrStr = env->GetObjectArrayElement( ( jobjectArray )adjMatr, i );
		env->GetDoubleArrayRegion( ( jdoubleArray )adjMatrStr, 0, vertexCount, fgraph.getAdjacentMatrixArr()[i] );
	}

	jmethodID getFcltPricesJMID = env->GetMethodID( graphClazz, "getFacilitiesPrices", "()[D" );
	assert( getFcltPricesJMID );
	jobject fcltPrices = env->CallObjectMethod( jfgraph, getFcltPricesJMID );
	assert( fcltPrices );
	env->GetDoubleArrayRegion( ( jdoubleArray )fcltPrices, 0, facilitiesCount, & fgraph.facilitiesPrices[0] );

	std::vector< char > isFacilityOpenVec;
	std::vector< int > cityWitnessVec;
	bool solved = ( * solverPtr )->solve( fgraph, isFacilityOpenVec, cityWitnessVec );
	assert( solved );
	assert( isFacilityOpenVec.size() == facilitiesCount );
	assert( cityWitnessVec.size() == citiesCount );
	env->SetBooleanArrayRegion( isFacilityOpen, 0, facilitiesCount, ( jboolean * ) ( & isFacilityOpenVec[0] ) );
	env->SetIntArrayRegion( cityWitness, 0, citiesCount, ( jint * ) ( & cityWitnessVec[0] ) );
}

JNIEXPORT void JNICALL Java_puntaelena_graphsupport_NativeGraphSupportBridge_nativeDisposeSolver
  ( JNIEnv *, jclass, jint solverIntPtr )
{
	IFacilityLocationSolverPtr * solverPtr =
		reinterpret_cast< IFacilityLocationSolverPtr * >( solverIntPtr );
	delete solverPtr;
	solverPtr = 0;
}
